/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <baja/common.hpp>
#include <baja/vars.hpp>
#include <baja/stopwatch.hpp>
#include <baja/graphics.hpp>
#include <baja/graphics/scenes.hpp>

namespace journal {

namespace b = baja;
namespace g = baja::graphics;
namespace gs = baja::graphics::scenes;
namespace gd = baja::graphics::dx11_1::details;

enum class performance_event
{
    renderFrame = 0,
    renderStroke = 1,
    inkSmoothing = 2,
    mipMapping = 3,
    input = 4,
    modelLoad = 5,
    modelSave = 6,
    normalCalculation = 7,
    deferredRender = 8,

    _count
};

struct iperformance_service : public baja::app::irender_observer
{
    BAJA_INTERFACE(iperformance_service, "9DF95A4E-5759-4B03-A192-B7106A22CF10")

    virtual void collect(const performance_event e, const double duration) = 0;
    virtual void reset(const performance_event e, bool restart = false) = 0;
    virtual void resetAll(bool restart = false) = 0;
    virtual void get(
        const performance_event e,
        b::float32* count,
        b::float32* value
        ) = 0;
};

// implementation
class performance_service : public iperformance_service, public isettings_service_observer, public std::enable_shared_from_this<performance_service>
{
public:
    performance_service() : _settingsObserverCookie(-1), _firstRender(true) {}

    static std::shared_ptr<performance_service> create(const std::shared_ptr<g::iengine>& graphics)
    {
        std::shared_ptr<performance_service> returnValue = std::make_shared<performance_service>();
        returnValue->init(graphics);
        return returnValue;
    }

    // iperformance_service
    void collect(const performance_event e, const double duration);
    void reset(const performance_event e, bool restart = false);
    void resetAll(bool restart = false);
    void get(
        const performance_event e,
        b::float32* xps,
        b::float32* mspx
        );

    // irender_observer
    void onRender();
    void onScreenChange(const a::screen_change_args& args) { /* noop */ };
    void onResize(const a::resize_args& args);
    void onVisibilityChanged(const bool visible) { /* noop */ }

    // isettings_service_observer
    void onBeforeSettingChange(const b::wchar* fullName);
    void onAfterSettingChange(const b::wchar* fullName);

private:
    const g::packed_color getColor(const performance_event e, b::uint32 alpha);
    std::shared_ptr<g::ifont> getFont();
    void init(const std::shared_ptr<g::iengine>& graphics);
    void calculatePositions(
        const int slot,
        const performance_event e,
        const b::wchar* topLabel, 
        const b::wchar* bottomLabel, 
        const b::float32 previousTop,
        m::rectangle* topLabelRect,
        m::rectangle* bottomLabelRect,
        m::rectangle* graphicRect,
        m::rectangle* totalRect
        );
    void renderSlot(const int slot, const performance_event e, b::float32& previousTop);
    const b::wchar* getName(const performance_event e);
    void renderGraphic(const int slot, const performance_event e, const m::rectangle* rect);
    m::point4d mapValue(int slot, double value, double max, int i, const m::rectangle* rect);

    struct performance_event_desc
    {
        performance_event_desc() : showGraph(false), label(nullptr), setting(nullptr), enabled(false) {}
        performance_event_desc(const bool showGraph_, const g::packed_color& color_, const b::wchar* label_, const b::wchar* setting_, const bool enabled_) :
            showGraph(showGraph_), color(color_), label(label_), setting(setting_), enabled(enabled_) {}

        bool showGraph;
        g::packed_color color;
        const b::wchar* label;
        const b::wchar* setting;
        bool enabled;
    };

    struct data_point
    {
        size_t count;
        double value;
    };

    struct circular_buffer
    {
        circular_buffer(const int size) : _over(false), _size(size), _current(-1) { _points.resize(size); }

        void add() 
        { 
            _current++; 
            if (_current >= _size) { _over = true; _current %= _size; }
        }

        data_point* at(const int i) { return (_current >= 0) ? &_points[index(i)] : nullptr; }
        int length() { return _over ? _size : _current+1; }
        data_point* current() { return at(length() - 1); }
        data_point* previous() { return at(length() - 2); }
        int index(const int i)
        {
            int first = (_over ? _current+1 : 0);
            return (first + i) % _size;
        }

        void reset()
        {
            _over = false;
            _current = -1;
        }

        bool _over;
        int _size;
        int _current;
        std::vector<data_point> _points;
    };

    struct data
    {
        data();
        bool dirty;
        bool init;
        circular_buffer buffer;
        double lastResetMs; // timestamp of the last reset
        double lastNonZero;
        double lastNonZeroAvg;
        std::recursive_mutex _mutex;
        data& operator=(const data& other);

        void reset()
        {
            dirty = false;
            init = false;
            buffer.reset();
            lastNonZero = 0;
            lastNonZeroAvg = 0;

        }
    };

    performance_event_desc _desc[performance_event::_count];
    data _data[performance_event::_count];
    b::stopwatch _sw;
    std::shared_ptr<g::ifont> _font;
    m::rectangle _vp;
    double _lastResetMs;

    static const b::float32 _margin;
    static const b::float32 _graphicHeight;
    static const b::float32 _graphicWidth;
    static const b::float32 _labelHeight;
    static const b::uint32 _backdropAlpha;

    bool _firstRender;
    b::int32 _settingsObserverCookie;
    std::map<const std::wstring, performance_event> _settingMap;
};

inline std::shared_ptr<iperformance_service> createPerformanceService(const std::shared_ptr<g::iengine>& graphics)
{
    std::shared_ptr<iperformance_service> returnValue;
    returnValue = std::dynamic_pointer_cast<iperformance_service>(performance_service::create(graphics));
    return returnValue;
}

} // namespace journal
